YUI.add('aui-carousel', function (A, NAME) {
    var Lang = A.Lang,

        STR_BLANK = ' ',

        CAROUSEL = 'carousel',

        getCN = A.ClassNameManager.getClassName,

        CSS_ITEM = getCN(CAROUSEL, 'item'),
        CSS_ITEM_ACTIVE = getCN(CAROUSEL, 'item', 'active'),
        CSS_ITEM_TRANSITION = getCN(CAROUSEL, 'item', 'transition'),
        CSS_MENU_ACTIVE = getCN(CAROUSEL, 'menu', 'active'),
        CSS_MENU_INDEX = getCN(CAROUSEL, 'menu', 'index'),
        CSS_MENU_ITEM = getCN(CAROUSEL, 'menu', 'item'),
        CSS_MENU_NEXT = getCN(CAROUSEL, 'menu', 'next'),
        CSS_MENU_PLAY = getCN(CAROUSEL, 'menu', 'play'),
        CSS_MENU_PAUSE = getCN(CAROUSEL, 'menu', 'pause'),
        CSS_MENU_PREV = getCN(CAROUSEL, 'menu', 'prev'),
        CSS_MENU_ITEM_DEFAULT = [CSS_MENU_ITEM, CSS_MENU_INDEX].join(STR_BLANK),
        CSS_MENU_ITEM_ACTIVE = [CSS_MENU_ITEM, CSS_MENU_INDEX, CSS_MENU_ACTIVE].join(STR_BLANK),

        DOT = '.',

        SELECTOR_MENU_INDEX = DOT + CSS_MENU_INDEX,
        SELECTOR_MENU_NEXT = DOT + CSS_MENU_NEXT,
        SELECTOR_MENU_PAUSE = DOT + CSS_MENU_PAUSE,
        SELECTOR_MENU_PLAY = DOT + CSS_MENU_PLAY,
        SELECTOR_MENU_PLAY_OR_PAUSE = [SELECTOR_MENU_PLAY, SELECTOR_MENU_PAUSE].join(),
        SELECTOR_MENU_PREV = DOT + CSS_MENU_PREV,

        TPL_ITEM = '<li><a class="' + CSS_MENU_ITEM + ' {cssClasses}">{index}</a></li>',

        TPL_MENU = '<menu>' +
            '<li><a class="' + CSS_MENU_ITEM + ' ' + CSS_MENU_PLAY + '"></a></li>' +
            '<li><a class="' + CSS_MENU_ITEM + ' ' + CSS_MENU_PREV + '"></a></li>' +
            '{items}' +
            '<li><a class="' + CSS_MENU_ITEM + ' ' + CSS_MENU_NEXT + '"></a></li>' +
            '</menu>',

        UI_SRC = A.Widget.UI_SRC,

        MAP_EVENT_INFO = {
            src: UI_SRC
        };

    /**
     * A base class for Carousel.
     *
     * Check the [live demo](http://alloyui.com/examples/carousel/).
     *
     * @class A.Carousel
     * @uses A.Component
     * @param config {Object} Object literal specifying widget configuration properties.
     * @constructor
     */
    var Carousel = A.Component.create({
        /**
         * Static property provides a string to identify the class.
         *
         * @property Carousel.NAME
         * @type String
         * @static
         */
        NAME: CAROUSEL,

        /**
         * Static property used to define the default attribute
         * configuration for the Carousel.
         *
         * @property Carousel.ATTRS
         * @type Object
         * @static
         */
        ATTRS: {

            /**
             * Index of the first visible item of the carousel.
             *
             * @attribute activeIndex
             * @default 0
             * @type Number
             */
            activeIndex: {
                value: 0,
                setter: '_setActiveIndex'
            },

            /**
             * Duration of the animation in seconds when change index on
             * Carousel.
             *
             * @attribute animationTime
             * @default 0.5
             * @type Number
             */
            animationTime: {
                value: 0.5
            },

            /**
             * Interval time in seconds between an item transition.
             *
             * @attribute intervalTime
             * @default 2
             * @type Number
             */
            intervalTime: {
                value: 2
            },

            /**
             * CSS Selector whitch determines the items to be loaded to the
             * Carousel.
             *
             * @attribute itemSelector
             * @default >* (All first childs)
             * @type String
             */
            itemSelector: {
                value: '>*'
            },

            /**
             * Node container of the navigation items.
             *
             * @attribute nodeMenu
             * @default null
             * @type Node | String
             */
            nodeMenu: {
                value: null,
                setter: '_setNodeMenu'
            },

            /**
             * CSS selector to match the navigation items.
             *
             * @attribute nodeMenuItemSelector
             * @default .carousel-menu-item
             * @type String
             */
            nodeMenuItemSelector: {
                value: DOT + CSS_MENU_ITEM
            },

            /**
             * Attributes that determines the status of transitions between
             * items.
             *
             * @attribute playing
             * @default true
             * @type Boolean
             */
            playing: {
                value: true
            }
        },

        prototype: {
            animation: null,
            nodeSelection: null,
            nodeMenu: null,

            /**
             * Construction logic executed during Carousel instantiation. Lifecycle.
             *
             * @method initializer
             * @protected
             */
            initializer: function() {
                var instance = this;

                instance.animation = new A.Anim({
                    duration: instance.get('animationTime'),
                    to: {
                        opacity: 1
                    }
                });
            },

            /**
             * Render the Carousel component instance. Lifecycle.
             *
             * @method renderUI
             * @protected
             */
            renderUI: function() {
                var instance = this;

                instance._updateNodeSelection();
                instance.nodeMenu = instance.get('nodeMenu');

                instance._updateMenuNodes();
            },

            /**
             * Bind the events on the Carousel UI. Lifecycle.
             *
             * @method bindUI
             * @protected
             */
            bindUI: function() {
                var instance = this;

                instance.after({
                    activeIndexChange: instance._afterActiveIndexChange,
                    animationTimeChange: instance._afterAnimationTimeChange,
                    itemSelectorChange: instance._afterItemSelectorChange,
                    intervalTimeChange: instance._afterIntervalTimeChange,
                    nodeMenuItemSelector: instance._afterNodeMenuItemSelectorChange,
                    playingChange: instance._afterPlayingChange
                });

                instance._bindMenu();

                if (instance.get('playing') === true) {
                    instance._afterPlayingChange({
                        prevVal: false,
                        newVal: true
                    });
                }
            },

            /**
             * Sync the Carousel UI. Lifecycle.
             *
             * @method syncUI
             * @protected
             */
            syncUI: function() {
                var instance = this;

                instance._uiSetActiveIndex(instance.get('activeIndex'));
            },

            /**
             * Set the <code>activeIndex</code> attribute which
             * activates a certain item on Carousel based on its index.
             *
             * @method item
             * @param val
             */
            item: function(val) {
                var instance = this;

                instance.set('activeIndex', val);
            },

            /**
             * Go to the next index.
             *
             * @method next
             */
            next: function() {
                var instance = this;

                instance._updateIndexNext();
            },

            /**
             * Set the <code>playing</code> attribute
             * to false which pauses the animation.
             *
             * @method pause
             */
            pause: function() {
                var instance = this;

                instance.set('playing', false);
            },

            /**
             * Set the <code>playing</code> attribute
             * to true which starts the animation.
             *
             * @method play
             */
            play: function() {
                var instance = this;

                instance.set('playing', true);
            },

            /**
             * Go to previous index.
             *
             * @method prev
             */
            prev: function() {
                var instance = this;

                instance._updateIndexPrev();
            },

            /**
             * Fire after <code>activeIndex</code> attribute changes.
             *
             * @method _afterActiveIndexChange
             * @param event
             * @protected
             */
            _afterActiveIndexChange: function(event) {
                var instance = this;

                instance._uiSetActiveIndex(
                    event.newVal, {
                        prevVal: event.prevVal,
                        animate: instance.get('playing'),
                        src: event.src
                    }
                );
            },

            /**
             * Fire after <code>animationTime</code> attribute changes.
             *
             * @method _afterAnimationTimeChange
             * @param event
             * @protected
             */
            _afterAnimationTimeChange: function(event) {
                var instance = this;

                instance.animation.set('duration', event.newVal);
            },

            /**
             * Fire after <code>itemSelector</code> attribute change.
             *
             * @method _afterItemSelectorChange
             * @param event
             * @protected
             */
            _afterItemSelectorChange: function(event) {
                var instance = this;

                instance._updateNodeSelection();
            },

            /**
             * Fire after <code>nodeMenuItemSelector</code> attribute change.
             *
             * @method _afterNodeMenuItemSelectorChange
             * @param event
             * @protected
             */
            _afterNodeMenuItemSelectorChange: function(event) {
                var instance = this;

                instance.nodeMenuItemSelector = event.newVal;

                instance._updateMenuNodes();
            },

            /**
             * Fire after <code>intervalTime</code> attribute changes.
             *
             * @method _afterIntervalTimeChange
             * @param event
             * @protected
             */
            _afterIntervalTimeChange: function(event) {
                var instance = this;

                instance._clearIntervalRotationTask();
                instance._createIntervalRotationTask();
            },

            /**
             * Fire after <code>playing</code> attribute changes.
             *
             * @method _afterPlayingChange
             * @param event
             * @protected
             */
            _afterPlayingChange: function(event) {
                var instance = this;

                var menuPlayItem = instance.nodeMenu.one(SELECTOR_MENU_PLAY_OR_PAUSE);
                var playing = event.newVal;

                var fromClass = CSS_MENU_PAUSE;
                var toClass = CSS_MENU_PLAY;

                var rotationTaskMethod = '_clearIntervalRotationTask';

                if (playing) {
                    fromClass = CSS_MENU_PLAY;
                    toClass = CSS_MENU_PAUSE;

                    rotationTaskMethod = '_createIntervalRotationTask';
                }

                instance[rotationTaskMethod]();

                if (menuPlayItem) {
                    menuPlayItem.replaceClass(fromClass, toClass);
                }
            },

            /**
             * Attach delegate to the carousel menu.
             *
             * @method _bindMenu
             * @protected
             */
            _bindMenu: function() {
                var instance = this;

                var menu = instance.nodeMenu;

                var nodeMenuItemSelector = instance.get('nodeMenuItemSelector');

                menu.delegate('click', instance._onClickDelegate, nodeMenuItemSelector, instance);

                instance.nodeMenuItemSelector = nodeMenuItemSelector;
            },

            /**
             * Clear the rotation task interval.
             *
             * @method _clearIntervalRotationTask
             * @protected
             */
            _clearIntervalRotationTask: function() {
                var instance = this;

                clearInterval(instance._intervalRotationTask);
            },

            /**
             * Create an random number to be current index.
             *
             * @method _createIndexRandom
             * @protected
             */
            _createIndexRandom: function() {
                var instance = this;

                return Math.ceil(Math.random() * instance.nodeSelection.size()) - 1;
            },

            /**
             * Create an timer for the rotation task.
             *
             * @method _createIntervalRotationTask
             * @protected
             */
            _createIntervalRotationTask: function() {
                var instance = this;

                instance._clearIntervalRotationTask();

                instance._intervalRotationTask = setInterval(
                    function() {
                        instance._updateIndexNext({
                            animate: true
                        });
                    },
                    instance.get('intervalTime') * 1000
                );
            },

            /**
             * Fire when animation ends.
             *
             * @method _onAnimationEnd
             * @param event
             * @param newImage
             * @param oldImage
             * @param newMenuItem
             * @param oldMenuItem
             * @protected
             */
            _onAnimationEnd: function(event, newImage, oldImage, newMenuItem, oldMenuItem) {
                var instance = this;

                if (oldImage) {
                    oldImage.removeClass(CSS_ITEM_TRANSITION);
                }

                newImage.setStyle('opacity', '1');
            },

            /**
             * Fire when animation starts.
             *
             * @method _onAnimationStart
             * @param event
             * @param newImage
             * @param oldImage
             * @param newMenuItem
             * @param oldMenuItem
             * @protected
             */
            _onAnimationStart: function(event, newImage, oldImage, newMenuItem, oldMenuItem) {
                var instance = this;

                newImage.addClass(CSS_ITEM_ACTIVE);

                if (newMenuItem) {
                    newMenuItem.addClass(CSS_MENU_ACTIVE);
                }

                if (oldImage) {
                    oldImage.replaceClass(CSS_ITEM_ACTIVE, CSS_ITEM_TRANSITION);
                }

                if (oldMenuItem) {
                    oldMenuItem.removeClass(CSS_MENU_ACTIVE);
                }
            },

            /**
             * Fire when a click is fired on menu.
             *
             * @method _onClickDelegate
             * @param event
             * @protected
             */
            _onClickDelegate: function(event) {
                var instance = this;

                event.preventDefault();

                var currentTarget = event.currentTarget;

                var handler;

                if (currentTarget.hasClass(CSS_MENU_INDEX)) {
                    handler = instance._onMenuItemClick;
                }
                else if (currentTarget.hasClass(CSS_MENU_PREV)) {
                    handler = instance._updateIndexPrev;
                }
                else if (currentTarget.hasClass(CSS_MENU_NEXT)) {
                    handler = instance._updateIndexNext;
                }
                else if (currentTarget.test(SELECTOR_MENU_PLAY_OR_PAUSE)) {
                    handler = instance._onMenuPlayClick;
                }

                if (handler) {
                    handler.apply(instance, arguments);
                }
            },

            /**
             * Execute when delegates handle menuItem click.
             *
             * @method _onMenuItemClick
             * @param event
             * @protected
             */
            _onMenuItemClick: function(event) {
                var instance = this;

                event.preventDefault();

                var newIndex = instance.menuNodes.indexOf(event.currentTarget);

                instance.set('activeIndex', newIndex, MAP_EVENT_INFO);
            },

            /**
             * Execute when delegates handle play click.
             *
             * @method _onMenuPlayClick
             * @param event
             * @protected
             */
            _onMenuPlayClick: function(event) {
                var instance = this;

                this.set('playing', !this.get('playing'));
            },

            /**
             * Render the menu in DOM.
             *
             * @method _renderMenu
             * @protected
             */
            _renderMenu: function() {
                var instance = this,
                    activeIndex = instance.get('activeIndex'),
                    items = [],
                    i,
                    len = instance.nodeSelection.size();

                for (i = 0; i < len; i++) {
                    items.push(
                        Lang.sub(TPL_ITEM, {
                            cssClasses: activeIndex === i ? CSS_MENU_ITEM_ACTIVE : CSS_MENU_ITEM_DEFAULT,
                            index: i
                        })
                    );
                }

                var menu = A.Node.create(Lang.sub(TPL_MENU, {
                    items: items.join(STR_BLANK)
                }));

                instance.get('contentBox').appendChild(menu);

                return menu;
            },

            /**
             * Set the <code>activeIndex</code> attribute.
             *
             * @method _setActiveIndex
             * @param val
             * @protected
             */
            _setActiveIndex: function(val) {
                var instance = this;

                if (val == 'rand') {
                    val = instance._createIndexRandom();
                }
                else {
                    val = Math.max(Math.min(val, instance.nodeSelection.size()), -1);
                }

                return val;
            },

            /**
             * Set the <code>nodeMenu</code> attribute.
             *
             * @method _setNodeMenu
             * @param val
             * @protected
             */
            _setNodeMenu: function(val) {
                var instance = this;

                return A.one(val) || instance._renderMenu();
            },

            /**
             * Set the <code>activeIndex</code> on the UI.
             *
             * @method _uiSetActiveIndex
             * @param newVal
             * @param objOptions
             * @protected
             */
            _uiSetActiveIndex: function(newVal, objOptions) {
                var instance = this;

                var oldImage = null;
                var oldMenuItem = null;
                var onStart = null;
                var onEnd = null;

                var newImage = instance.nodeSelection.item(newVal);

                var menuNodes = instance.menuNodes;

                var newMenuItem = menuNodes.item(newVal);

                instance.animation.set('node', newImage);

                if (objOptions && !Lang.isUndefined(objOptions.prevVal)) {
                    var prevVal = objOptions.prevVal;

                    newImage.setStyle('opacity', '0');

                    oldMenuItem = menuNodes.item(prevVal);
                    oldImage = instance.nodeSelection.item(prevVal);

                    oldImage.replaceClass(CSS_ITEM_ACTIVE, CSS_ITEM_TRANSITION);

                    instance.animation.stop();
                }
                else {
                    newImage.addClass(CSS_ITEM_ACTIVE);

                    newImage.setStyle('opacity', '1');
                }

                onStart = instance.animation.on(
                    'start',
                    function(event) {
                        instance._onAnimationStart(event, newImage, oldImage, newMenuItem, oldMenuItem);

                        onStart.detach();
                    }
                );

                onEnd = instance.animation.on(
                    'end',
                    function(event) {
                        instance._onAnimationEnd(event, newImage, oldImage, newMenuItem, oldMenuItem);

                        onEnd.detach();
                    }
                );

                if (objOptions) {
                    if (objOptions.animate) {
                        instance.animation.run();
                    }
                    else {
                        instance.animation.fire('start');
                        instance.animation.fire('end');
                    }

                    if (objOptions.src == UI_SRC && objOptions.animate) {
                        instance._createIntervalRotationTask();
                    }
                }
            },

            /**
             * Set the <code>activeIndex</code> to the next index.
             *
             * @method _updateIndexNext
             * @param options
             * @protected
             */
            _updateIndexNext: function(options) {
                var instance = this;

                var currentIndex = instance.get('activeIndex');
                var nodeSelectionSize = instance.nodeSelection.size();

                var newIndex = currentIndex + 1;

                if (newIndex > (nodeSelectionSize - 1)) {
                    newIndex = 0;
                }

                if (options) {
                    options.src = UI_SRC;
                }

                instance.set('activeIndex', newIndex, options);
            },

            /**
             * Set the <code>activeIndex</code> to the previous index.
             *
             * @method _updateIndexPrev
             * @param options
             * @protected
             */
            _updateIndexPrev: function(options) {
                var instance = this;

                var currentIndex = instance.get('activeIndex');

                var newIndex = currentIndex - 1;

                if (newIndex < 0) {
                    newIndex = instance.nodeSelection.size() - 1;
                }

                if (options) {
                    options.src = UI_SRC;
                }

                instance.set('activeIndex', newIndex, options);
            },

            /**
             * Set the <code>menuNodes</code> attribute based on the selector menu index.
             *
             * @method _updateMenuNodes
             * @param options
             * @protected
             */
            _updateMenuNodes: function() {
                var instance = this;

                instance.menuNodes = instance.nodeMenu.all(SELECTOR_MENU_INDEX);
            },

            /**
             * Update the <code>nodeSelection</code> by adding the CSS_ITEM class.
             *
             * @method _updateMenuNodes
             * @param options
             * @protected
             */
            _updateNodeSelection: function() {
                var instance = this;

                var itemSelector = instance.get('itemSelector');

                var nodeSelection = instance.get('contentBox').all(itemSelector);

                nodeSelection.addClass(CSS_ITEM);

                instance.nodeSelection = nodeSelection;
            },

            _intervalRotationTask: null
        }
    });

    A.Carousel = Carousel;


}, '2.0.0', {"requires": ["anim", "node-event-delegate", "aui-component"], "skinnable": true});